/*
 * BLTray.Program
 * Andy Tidball
 * 
 * Project: Black Lightning Tray Tool
 * Copyright: GNU General Public License
 */

using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using System.Windows.Forms.Design;
using BLTray.Properties;
using BLLib;
using BLLib.Configuration;
using BLLib.Windows.Forms;
using BLLib.Xml;

namespace BLTray {
	/// <summary>
	/// A class that represents the application itself.
	/// </summary>
	internal static class Program {
		// member data
		private static NotifyIcon           _TrayIcon        = null; // our tray icon
		private static ContextMenuStrip     _ContextMenu     = null; // our main context menu
		private static OptionSet            _RootOptions     = null; // the set of options representing the main context menu
		private static ImageList            _Images          = null; // the set of images we'll be displaying
		private static OptionSetIndexSorter _Sorter          = null; // a sorter for sorting option sets
		private static OptionDialog         _CustomizeDialog = null; // the customize dialog
		private static OptionSet            _Default         = null; // our default menu item
		
		// member data - base option sets
		private static OptionSet _BlackLightningOptions = null;
		private static OptionSet _WebSiteOptions        = null;
		private static OptionSet _ApplicationOptions    = null;
		private static OptionSet _SeparatorOptions      = null;

		/// <summary>
		/// The main entry point for the application.
		/// </summary>
		[STAThread]
		static void Main(string[] Args) {
			// setup basic member data
			_Sorter = new OptionSetIndexSorter();

			// create our base option sets
			_BlackLightningOptions = InitializeBlackLightningOptions(new OptionSet(Resources.BlackLightningMenuItemName));
			_WebSiteOptions        = InitializeWebSiteOptions(new OptionSet(Resources.WebSiteMenuItemName));
			_ApplicationOptions    = InitializeApplicationOptions(new OptionSet(Resources.ApplicationMenuItemName));
			_SeparatorOptions      = InitializeSeparatorOptions(new OptionSet(Resources.SeparatorMenuItemName));

			// load our default images into our list
			_Images = new ImageList();
			_Images.Images.Add(Resources.BlackLightningIcon);
			_Images.Images.Add(Resources.GlobeIcon);
			_Images.Images.Add(Resources.ApplicationIcon);
			_Images.Images.Add(Resources.SeparatorIcon);
			
			// load in our root option set
			_RootOptions = new OptionSet(Resources.ApplicationName);
			try {
				XmlFile In = new XmlFile(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + Path.DirectorySeparatorChar + Resources.BlackLightningOptionsFolder + Path.DirectorySeparatorChar + Resources.OptionsFilename);
				_RootOptions.Load(In.RootTag);
			} catch (FileNotFoundException) {
				// if we couldn't load options it's no big deal, we'll just create blank ones
			}

			// initialize our options
			InitializeOptions(_RootOptions);

			// find our default option
			_Default = FindDefaultOptionSet(_RootOptions);

			// create our context menu
			_ContextMenu           = new ContextMenuStrip();
			_ContextMenu.ImageList = _Images;
			_ContextMenu.Tag       = _RootOptions;

			// build the menu
			BuildMenu();

			// create our customize dialog
			_CustomizeDialog                       = new OptionDialog();
			_CustomizeDialog.AllowOptionSetEditing = true;
			_CustomizeDialog.Icon                  = Resources.BlackLightningIcon;
			_CustomizeDialog.ImageList             = _Images;
			_CustomizeDialog.RootOptionSet         = _RootOptions;
			_CustomizeDialog.ShowOptionLevels      = false;
			_CustomizeDialog.Sorter                = _Sorter;
			_CustomizeDialog.AddOptionSetType(_BlackLightningOptions);
			_CustomizeDialog.AddOptionSetType(_WebSiteOptions);
			_CustomizeDialog.AddOptionSetType(_ApplicationOptions);
			_CustomizeDialog.AddOptionSetType(_SeparatorOptions);

			// create our tray icon
			_TrayIcon                  = new NotifyIcon();
			_TrayIcon.ContextMenuStrip = _ContextMenu;
			_TrayIcon.Icon             = Resources.BlackLightningIcon;
			_TrayIcon.Text             = Resources.ToolTip;
			_TrayIcon.Visible          = true;
			_TrayIcon.DoubleClick     += TrayIcon_DoubleClick;

			// run the application's message loop
			SingletonApplication.Run(Args);
		}

		/// <summary>
		/// Builds the context menu for the tray icon.
		/// </summary>
		private static void BuildMenu() {
			// clear out the current menu, if there is one
			_ContextMenu.Items.Clear();

			// add one item to the list for each option set in the root
			foreach (OptionSet OS in _RootOptions.GetSortedChildren(_Sorter)) {
				AddMenuItem(_ContextMenu, OS);
			}

			// add the always present options
			_ContextMenu.Items.Add(new ToolStripSeparator());
			ToolStripMenuItem MI = new ToolStripMenuItem();
			MI.Text = Resources.CustomizeMenuItemText;
			MI.ImageIndex = -1;
			MI.Click += Customize_Click;
			_ContextMenu.Items.Add(MI);
			MI = new ToolStripMenuItem();
			MI.Text = Resources.ExitMenuItemText;
			MI.ImageIndex = -1;
			MI.Click += Exit_Click;
			_ContextMenu.Items.Add(MI);
		}

		/// <summary>
		/// Adds a new item to the given menu which is based on the specified OptionSet.
		/// </summary>
		/// <param name="Menu">The menu that the new item will appear on.</param>
		/// <param name="OS">The OptionSet to create the new menu item out of.</param>
		private static void AddMenuItem(ToolStripDropDown Menu, OptionSet OS) {
			// check if this is a separator or an actual item
			Option O = OS.GetOption(Resources.TypeOptionName);
			if (O.Value as string == MenuItemType.Separator.ToString()) {
				Menu.Items.Add(new ToolStripSeparator());
			} else {
				// create our menu item
				ToolStripMenuItem MI = new ToolStripMenuItem();
				MI.ImageIndex = OS.IconIndex;
				MI.Tag        = OS;
				MI.Text       = OS.Name;
				MI.Click     += MenuItem_Click;

				// check if we're going to have a sub-menu
				if (OS.Children.Count > 0) {
					// carry down values from the old menu to the new
					MI.DropDown = new ContextMenuStrip();
					MI.DropDown.ImageList = Menu.ImageList;

					// create child menu items for each of this set's children
					foreach (OptionSet Child in OS.GetSortedChildren(_Sorter)) {
						AddMenuItem(MI.DropDown, Child);
					}
				}

				// if this is the default option set, make it bold
				if (OS == _Default) {
					MI.Font = new Font(MI.Font, FontStyle.Bold);
				}

				// add the item to the menu
				Menu.Items.Add(MI);
			}
		}

		/// <summary>
		/// Finds the OptionSet which has its Default Option set to True.
		/// </summary>
		/// <param name="OS">The OptionSet to start the search at.</param>
		/// <returns>The OptionSet which is the default action, or null if none is marked as default.</returns>
		private static OptionSet FindDefaultOptionSet(OptionSet OS) {
			Option O = OS.GetOption(Resources.DefaultActionOptionName);
			if ( (O != null) && ((bool)O.Value) ) {
				// we found the default, return it
				return OS;
			} else {
				// this one isn't it, check its children
				foreach (OptionSet Child in OS.Children) {
					OptionSet Default = FindDefaultOptionSet(Child);
					if (Default != null) {
						// this child is it, return it
						return Default;
					}
				}
			}

			// didn't find it, return null
			return null;
		}

		/// <summary>
		/// Initializes all the Options and OptionSets within the given set.
		/// </summary>
		/// <param name="OS">The OptionSet to initialize all the Options in.</param>
		private static void InitializeOptions(OptionSet OS) {
			// find the type option so we know what else to initialize
			Option TypeOption = OS.GetOption(Resources.TypeOptionName);
			if (TypeOption != null) {
				if (TypeOption.Value as string == MenuItemType.BlackLightning.ToString()) {
					InitializeBlackLightningOptions(OS);
				} else if (TypeOption.Value as string == MenuItemType.WebSite.ToString()) {
					InitializeWebSiteOptions(OS);
				} else if (TypeOption.Value as string == MenuItemType.Application.ToString()) {
					InitializeApplicationOptions(OS);
				} else if (TypeOption.Value as string == MenuItemType.Separator.ToString()) {
					InitializeSeparatorOptions(OS);
				} else {
					throw new ArgumentException(string.Format(Resources.ErrorUnknownTypeOption, TypeOption.Value as string));
				}
			}

			// find our name
			Option TextOption = OS.GetOption(Resources.TextOptionName);
			if (TextOption != null) {
				OS.Name = TextOption.Value as string;
			}

			// setup our image index
			Option IconOption = OS.GetOption(Resources.IconOptionName);
			if (IconOption != null) {
				string Filename = IconOption.Value as string;
				if (File.Exists(Filename)) {
					_Images.Images.Add(new Icon(Filename));
					OS.IconIndex = _Images.Images.Count-1;
					OS.SelectedIconIndex = OS.IconIndex;
				}
			}

			// run through our child sets and initialize them as well
			foreach (OptionSet Child in OS.Children) {
				InitializeOptions(Child);
			}
		}

		/// <summary>
		/// Initializes the given Option from a BlackLightning OptionSet.
		/// </summary>
		/// <param name="O">The Option to initialize.</param>
		private static void InitializeBlackLightningOption(Option O) {
			// initialize the option appropriately
			       if (O.Name == Resources.TypeOptionName) {           O.Initialize(new OptionInitializer(typeof(string), MenuItemType.BlackLightning.ToString(), Resources.TypeOptionDescription,          Resources.TypeOptionCategory,          true,  true));
			} else if (O.Name == Resources.TextOptionName) {           O.Initialize(new OptionInitializer(typeof(string), Resources.BlackLightningMenuItemName,   Resources.TextOptionDescription,          Resources.TextOptionCategory));
			} else if (O.Name == Resources.IconOptionName) {           O.Initialize(new OptionInitializer(typeof(string), Application.StartupPath + Path.DirectorySeparatorChar + Resources.IconsFolder + Path.DirectorySeparatorChar + Resources.BlackLightningMenuItemIcon, Resources.IconOptionDescription, Resources.IconOptionCategory, false, false, null, typeof(IconPathTypeEditor)));
			} else if (O.Name == Resources.DefaultActionOptionName) {  O.Initialize(new OptionInitializer(typeof(bool),   false,                                  Resources.DefaultActionOptionDescription, Resources.DefaultActionOptionCategory));
			} else if (O.Name == Resources.LoginClientOptionName) {    O.Initialize(new OptionInitializer(typeof(string), Resources.LoginClientOptionDefault,     Resources.LoginClientOptionDescription,   Resources.LoginClientOptionCategory));
			} else if (O.Name == Resources.ArgumentsOptionName) {      O.Initialize(new OptionInitializer(typeof(string), string.Empty,                           Resources.ArgumentsOptionDescription,     Resources.ArgumentsOptionCategory));
			} else if (O.Name == Resources.UsernameOptionName) {       O.Initialize(new OptionInitializer(typeof(string), string.Empty,                           Resources.UsernameOptionDescription,      Resources.UsernameOptionCategory));
			} else if (O.Name == Resources.PasswordOptionName) {       O.Initialize(new OptionInitializer(typeof(string), string.Empty,                           Resources.PasswordOptionDescription,      Resources.PasswordOptionCategory,      false, false, Resources.PasswordEncryptionKey, typeof(PasswordTypeEditor), typeof(PasswordTypeConverter)));
			} else if (O.Name == Resources.GameCodeOptionName) {       O.Initialize(new OptionInitializer(typeof(string), string.Empty,                           Resources.GameCodeOptionDescription,      Resources.GameCodeOptionCategory));
			} else if (O.Name == Resources.CharacterCodeOptionName) {  O.Initialize(new OptionInitializer(typeof(string), string.Empty,                           Resources.CharacterCodeOptionDescription, Resources.CharacterCodeOptionCategory));
			} else if (O.Name == Resources.FrontEndOptionName) {       O.Initialize(new OptionInitializer(typeof(string), string.Empty,                           Resources.FrontEndOptionDescription,      Resources.FrontEndOptionCategory));
			}

			// add our event handlers
			if (O.Name == Resources.TextOptionName) {
					O.ValueChanged += TextOption_ValueChanged;
					O.ValueChanging += TextOption_ValueChanging;
			} else if (O.Name == Resources.IconOptionName) {
					O.ValueChanged += IconOption_ValueChanged;
					O.ValueChanging += IconOption_ValueChanging;
			} else if (O.Name == Resources.DefaultActionOptionName) {
					O.ValueChanging += DefaultOption_ValueChanging;
			}
		}
		
		/// <summary>
		/// Initializes the given OptionSet to represent a Black Lightning execution menu item.
		/// </summary>
		/// <param name="OS">The OptionSet to initialize.</param>
		/// <returns>An initialized OptionSet with all the Options necessary to execute Black Lightning.</returns>
		private static OptionSet InitializeBlackLightningOptions(OptionSet OS) {
			// setup default icon indices
			OS.IconIndex = 0;
			OS.SelectedIconIndex = 0;

			// create its options
			InitializeBlackLightningOption(OS.GetOrCreateOption(Resources.TypeOptionName));
			InitializeBlackLightningOption(OS.GetOrCreateOption(Resources.TextOptionName));
			InitializeBlackLightningOption(OS.GetOrCreateOption(Resources.IconOptionName));
			InitializeBlackLightningOption(OS.GetOrCreateOption(Resources.DefaultActionOptionName));
			InitializeBlackLightningOption(OS.GetOrCreateOption(Resources.LoginClientOptionName));
			InitializeBlackLightningOption(OS.GetOrCreateOption(Resources.ArgumentsOptionName));
			InitializeBlackLightningOption(OS.GetOrCreateOption(Resources.UsernameOptionName));
			InitializeBlackLightningOption(OS.GetOrCreateOption(Resources.PasswordOptionName));
			InitializeBlackLightningOption(OS.GetOrCreateOption(Resources.GameCodeOptionName));
			InitializeBlackLightningOption(OS.GetOrCreateOption(Resources.CharacterCodeOptionName));
			InitializeBlackLightningOption(OS.GetOrCreateOption(Resources.FrontEndOptionName));
			
			// return the set
			return OS;
		}

		/// <summary>
		/// Initializes the given Option from a WebSite OptionSet.
		/// </summary>
		/// <param name="O">The Option to initialize.</param>
		private static void InitializeWebSiteOption(Option O) {
			// initialize the option appropriately
			       if (O.Name == Resources.TypeOptionName) {           O.Initialize(new OptionInitializer(typeof(string), MenuItemType.WebSite.ToString(), Resources.TypeOptionDescription,          Resources.TypeOptionCategory, true, true));
			} else if (O.Name == Resources.TextOptionName) {           O.Initialize(new OptionInitializer(typeof(string), Resources.WebSiteMenuItemName,   Resources.TextOptionDescription,          Resources.TextOptionCategory));
			} else if (O.Name == Resources.IconOptionName) {           O.Initialize(new OptionInitializer(typeof(string), Application.StartupPath + Path.DirectorySeparatorChar + Resources.IconsFolder + Path.DirectorySeparatorChar + Resources.WebSiteMenuItemIcon, Resources.IconOptionDescription, Resources.IconOptionCategory, false, false, null, typeof(IconPathTypeEditor)));
			} else if (O.Name == Resources.DefaultActionOptionName) {  O.Initialize(new OptionInitializer(typeof(bool),   false,                           Resources.DefaultActionOptionDescription, Resources.DefaultActionOptionCategory));
			} else if (O.Name == Resources.URLOptionName) {            O.Initialize(new OptionInitializer(typeof(string), Resources.URLOptionDefault,      Resources.URLOptionDescription,           Resources.URLOptionCategory));
			}

			// add our event handlers
			if (O.Name == Resources.TextOptionName) {
				O.ValueChanged += TextOption_ValueChanged;
				O.ValueChanging += TextOption_ValueChanging;
			} else if (O.Name == Resources.IconOptionName) {
				O.ValueChanged += IconOption_ValueChanged;
				O.ValueChanging += IconOption_ValueChanging;
			} else if (O.Name == Resources.DefaultActionOptionName) {
				O.ValueChanging += DefaultOption_ValueChanging;
			}
		}

		/// <summary>
		/// Initializes the given OptionSet to represent a Web Site execution menu item.
		/// </summary>
		/// <param name="OS">The OptionSet to initialize.</param>
		/// <returns>An initialized OptionSet with all the Options necessary to execute a Web Site.</returns>
		private static OptionSet InitializeWebSiteOptions(OptionSet OS) {
			// setup default icon indices
			OS.IconIndex = 1;
			OS.SelectedIconIndex = 1;

			// create its options
			InitializeWebSiteOption(OS.GetOrCreateOption(Resources.TypeOptionName));
			InitializeWebSiteOption(OS.GetOrCreateOption(Resources.TextOptionName));
			InitializeWebSiteOption(OS.GetOrCreateOption(Resources.IconOptionName));
			InitializeWebSiteOption(OS.GetOrCreateOption(Resources.DefaultActionOptionName));
			InitializeWebSiteOption(OS.GetOrCreateOption(Resources.URLOptionName));

			// return the set
			return OS;
		}
		
		/// <summary>
		/// Initializes the given Option from an Application OptionSet.
		/// </summary>
		/// <param name="O">The Option to initialize.</param>
		private static void InitializeApplicationOption(Option O) {
			// initialize the option appropriately
			       if (O.Name == Resources.TypeOptionName) {          O.Initialize(new OptionInitializer(typeof(string), MenuItemType.Application.ToString(), Resources.TypeOptionDescription,          Resources.TypeOptionCategory, true, true));
			} else if (O.Name == Resources.TextOptionName) {          O.Initialize(new OptionInitializer(typeof(string), Resources.ApplicationMenuItemName,   Resources.TextOptionDescription,          Resources.TextOptionCategory));
			} else if (O.Name == Resources.IconOptionName) {          O.Initialize(new OptionInitializer(typeof(string), Application.StartupPath + Path.DirectorySeparatorChar + Resources.IconsFolder + Path.DirectorySeparatorChar + Resources.ApplicationMenuItemIcon, Resources.IconOptionDescription, Resources.IconOptionCategory, false, false, null, typeof(IconPathTypeEditor))); 
			} else if (O.Name == Resources.DefaultActionOptionName) { O.Initialize(new OptionInitializer(typeof(bool),   false,                               Resources.DefaultActionOptionDescription, Resources.DefaultActionOptionCategory));
			} else if (O.Name == Resources.ApplicationOptionName) {   O.Initialize(new OptionInitializer(typeof(string), string.Empty,                        Resources.ApplicationOptionDescription,   Resources.ApplicationOptionCategory, false, false, null, typeof(ExecutablePathTypeEditor)));
			} else if (O.Name == Resources.ArgumentsOptionName) {     O.Initialize(new OptionInitializer(typeof(string), string.Empty,                        Resources.ArgumentsOptionDescription,     Resources.ArgumentsOptionCategory));
			}

			// add our event handlers
			if (O.Name == Resources.TextOptionName) {
				O.ValueChanged += TextOption_ValueChanged;
				O.ValueChanging += TextOption_ValueChanging;
			} else if (O.Name == Resources.IconOptionName) {
				O.ValueChanged += IconOption_ValueChanged;
				O.ValueChanging += IconOption_ValueChanging;
			} else if (O.Name == Resources.DefaultActionOptionName) {
				O.ValueChanging += DefaultOption_ValueChanging;
			}
		}

		/// <summary>
		/// Initializes the given OptionSet to represent an arbitrary application execution menu item.
		/// </summary>
		/// <param name="OS">The OptionSet to initialize.</param>
		/// <returns>An initialized OptionSet with all the Options necessary to execute an arbitrary application.</returns>
		private static OptionSet InitializeApplicationOptions(OptionSet OS) {
			// setup default icon indices
			OS.IconIndex = 2;
			OS.SelectedIconIndex = 2;

			// create its options
			InitializeApplicationOption(OS.GetOrCreateOption(Resources.TypeOptionName));
			InitializeApplicationOption(OS.GetOrCreateOption(Resources.TextOptionName));
			InitializeApplicationOption(OS.GetOrCreateOption(Resources.IconOptionName));
			InitializeApplicationOption(OS.GetOrCreateOption(Resources.DefaultActionOptionName));
			InitializeApplicationOption(OS.GetOrCreateOption(Resources.ApplicationOptionName));
			InitializeApplicationOption(OS.GetOrCreateOption(Resources.ArgumentsOptionName));

			// return the set
			return OS;
		}

		/// <summary>
		/// Initializes the given Option from a Separator OptionSet.
		/// </summary>
		/// <param name="O">The Option to initialize.</param>
		private static void InitializeSeparatorOption(Option O) {
			// initialize the option appropriately
			       if (O.Name == Resources.TypeOptionName) { O.Initialize(new OptionInitializer(typeof(string), MenuItemType.Separator.ToString(), Resources.TypeOptionDescription, Resources.TypeOptionCategory, true, true));
			} else if (O.Name == Resources.TextOptionName) { O.Initialize(new OptionInitializer(typeof(string), Resources.SeparatorMenuItemName,   Resources.TextOptionDescription, Resources.TextOptionCategory));
			}
			
			// add our event handlers
			if (O.Name == Resources.TextOptionName) {
				O.ValueChanged += TextOption_ValueChanged;
				O.ValueChanging += TextOption_ValueChanging;
			}
		}

		/// <summary>
		/// Initializes the given OptionSet to represent a separator menu item.
		/// </summary>
		/// <param name="OS">The OptionSet to initialize.</param>
		/// <returns>An initialized OptionSet with all the Options necessary to display a separator.</returns>
		private static OptionSet InitializeSeparatorOptions(OptionSet OS) {
			// setup default icon indices
			OS.IconIndex = 3;
			OS.SelectedIconIndex = 3;

			// create its options
			InitializeSeparatorOption(OS.GetOrCreateOption(Resources.TypeOptionName));
			InitializeSeparatorOption(OS.GetOrCreateOption(Resources.TextOptionName));

			// return the set
			return OS;
		}

		/// <summary>
		/// Executes the action represented by the given OptionSet.
		/// </summary>
		/// <param name="OS">The OptionSet to execute.</param>
		private static void ExecuteOption(OptionSet OS) {
			// check what type of item it is
			Option TypeOption = OS.GetOption(Resources.TypeOptionName);
			string Type = TypeOption.Value as string;
			if (Type == MenuItemType.Application.ToString()) {
				ExecuteApplicationOption(OS);
			} else if (Type == MenuItemType.BlackLightning.ToString()) {
				ExecuteBlackLightningOption(OS);
			} else if (Type == MenuItemType.WebSite.ToString()) {
				ExecuteWebSiteOption(OS);
			}
		}

		/// <summary>
		/// Executes a Application menu item.
		/// </summary>
		/// <param name="OS">The OptionSet pertaining to the menu item.</param>
		private static void ExecuteApplicationOption(OptionSet OS) {
			Option AppOption  = OS.GetOption(Resources.ApplicationOptionName);
			Option ArgsOption = OS.GetOption(Resources.ArgumentsOptionName);
			string Filename   = AppOption.Value as string;
			string Arguments  = ArgsOption.Value as string;
			try {
				Process.Start(Filename, Arguments);
			} catch (Exception x) {
				MessageBox.Show(string.Format(Resources.ErrorCannotExecuteApplication, Filename, x.Message), Resources.ErrorMessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error);
			}
		}

		/// <summary>
		/// Executes a Black Lightning menu item.
		/// </summary>
		/// <param name="OS">The OptionSet pertaining to the menu item.</param>
		private static void ExecuteBlackLightningOption(OptionSet OS) {
			Option LCOption = OS.GetOption(Resources.LoginClientOptionName);
			string LoginClient = LCOption.Value as string;
			string Arguments;
			if (LoginClient == Resources.AutoLoginClientName) {
				// build the arguments
				string Username = OS.GetOption(Resources.UsernameOptionName).Value as string;
				string Password = OS.GetOption(Resources.PasswordOptionName).Value as string;
				string GameCode = OS.GetOption(Resources.GameCodeOptionName).Value as string;
				string CharCode = OS.GetOption(Resources.CharacterCodeOptionName).Value as string;
				string FECode   = OS.GetOption(Resources.FrontEndOptionName).Value as string;
				Arguments = string.Format(Resources.AutoLoginClientArguments, Username, Password, GameCode, CharCode, FECode);
			} else {
				Arguments = OS.GetOption(Resources.ArgumentsOptionName).Value as string;
			}

			Arguments = LoginClient + " " + Arguments;
			try {
				Process.Start(Application.StartupPath + Path.DirectorySeparatorChar + Resources.BlackLightningFilename, Arguments);
			} catch (Exception x) {
				MessageBox.Show(string.Format(Resources.ErrorCannotExecuteApplication, Resources.BlackLightningApplicationName, x.Message), Resources.ErrorMessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error);
			}
		}

		/// <summary>
		/// Executes a Web Site menu item.
		/// </summary>
		/// <param name="OS">The OptionSet pertaining to the menu item.</param>
		private static void ExecuteWebSiteOption(OptionSet OS) {
			Option URLOption = OS.GetOption(Resources.URLOptionName);
			string URL = URLOption.Value as string;
			try {
				Process.Start(URL);
			} catch (Exception x) {
				MessageBox.Show(string.Format(Resources.ErrorCannotLaunchURL, URL, x.Message), Resources.ErrorMessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error);
			}
		}

		/// <summary>
		/// Called when the Customize menu option is clicked.
		/// </summary>
		/// <param name="Sender">The ToolStripMenuItem that was clicked.</param>
		/// <param name="e">No arguments.</param>
		private static void Customize_Click(object Sender, EventArgs e) {
			// give the dialog a clone of our option set, then display the dialog
			_CustomizeDialog.RootOptionSet = _RootOptions.Clone() as OptionSet;
			if (_CustomizeDialog.ShowDialog() != DialogResult.OK) {
				return;
			}
			
			// store the root set from the dialog back to ours
			_RootOptions = _CustomizeDialog.RootOptionSet;

			// find the new default set
			_Default = FindDefaultOptionSet(_RootOptions);

			// save the new settings to our options file
			XmlFile Out = new XmlFile(_RootOptions.Save(false));
			if (Out.RootTag != null) {
				Out.Save(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + Path.DirectorySeparatorChar + Resources.BlackLightningOptionsFolder + Path.DirectorySeparatorChar + Resources.OptionsFilename);
			} else {
				File.Delete(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + Path.DirectorySeparatorChar + Resources.BlackLightningOptionsFolder + Path.DirectorySeparatorChar + Resources.OptionsFilename);
			}

			// re-build the menu
			BuildMenu();
		}

		/// <summary>
		/// Called when the Exit menu option is clicked.
		/// </summary>
		/// <param name="Sender">The ToolStripMenuItem that was clicked.</param>
		/// <param name="e">No arguments.</param>
		private static void Exit_Click(object Sender, EventArgs e) {
			// hide the notify icon and exit
			_TrayIcon.Visible = false;
			SingletonApplication.Exit();
		}

		/// <summary>
		/// Called when any of our Icon Options changes value.
		/// </summary>
		/// <param name="Sender">The Option whose value changed.</param>
		/// <param name="e">Arguments about the change.</param>
		private static void IconOption_ValueChanged(object Sender, OptionValueChangedEventArgs e) {
			Option O = Sender as Option;
			_Images.Images[O.Parent.IconIndex] = new Icon(e.NewValue as string).ToBitmap();
			_CustomizeDialog.Refresh();
		}

		/// <summary>
		/// Called when any of our Icon Options is about to change value.
		/// </summary>
		/// <param name="Sender">The Option whose value is changing.</param>
		/// <param name="e">Arguments about the change.</param>
		private static void IconOption_ValueChanging(object Sender, OptionValueChangingEventArgs e) {
			string Filename = e.NewValue as string;
			try {
				Icon I = new Icon(Filename);
			} catch (Exception x) {
				MessageBox.Show(_CustomizeDialog, string.Format(Resources.ErrorInvalidIconFilename, Filename, x.Message), Resources.ErrorMessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error);
				e.Cancel = true;
			}
		}

		/// <summary>
		/// Called when any of our Text Options changes value.
		/// </summary>
		/// <param name="Sender">The Option whose value changed.</param>
		/// <param name="e">Arguments about the change.</param>
		private static void TextOption_ValueChanged(object Sender, OptionValueChangedEventArgs e) {
			Option O = Sender as Option;
			O.Parent.Name = e.NewValue as string;
			_CustomizeDialog.RefreshDisplay();
		}

		/// <summary>
		/// Called when any of our Text Options is about to change value.
		/// </summary>
		/// <param name="Sender">The Option whose value is changing.</param>
		/// <param name="e">Arguments about the change.</param>
		private static void TextOption_ValueChanging(object Sender, OptionValueChangingEventArgs e) {
			// grab the changing option's parent's parent
			Option O = Sender as Option;
			OptionSet OS = O.Parent.Parent;

			// make sure the parent has no child sets with the new name
			string NewName = e.NewValue as string;
			foreach (OptionSet Child in OS.Children) {
				if (Child.Name == NewName) {
					MessageBox.Show(_CustomizeDialog, string.Format(Resources.ErrorDuplicateMenuItemText, e.OldValue as string, e.NewValue as string), Resources.ErrorMessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error);
					e.Cancel = true;
					return;
				}
			}
		}

		/// <summary>
		/// Called when any of our Default Action Options changes value.
		/// </summary>
		/// <param name="Sender">The Option whose value changed.</param>
		/// <param name="e">Arguments about the change.</param>
		private static void DefaultOption_ValueChanging(object Sender, OptionValueChangingEventArgs e) {
			// if the value is changing to false, we don't care
			if (!(bool)e.NewValue) {
				return;
			}

			// find the set that is the current default and turn it off
			OptionSet CurrentDefault = FindDefaultOptionSet(_CustomizeDialog.RootOptionSet);
			if (CurrentDefault != null) {
				Option O = CurrentDefault.GetOption(Resources.DefaultActionOptionName);
				O.Value = false;
				O.ApplyNewValue();
			}
		}

		/// <summary>
		/// Called when any menu item is clicked.
		/// </summary>
		/// <param name="Sender">The ToolStripMenuItem that was clicked.</param>
		/// <param name="e">No arguments.</param>
		private static void MenuItem_Click(object Sender, EventArgs e) {
			// grab the related option set and execute it
			ToolStripMenuItem MI = Sender as ToolStripMenuItem;
			ExecuteOption(MI.Tag as OptionSet);
		}

		/// <summary>
		/// Called when our tray icon is double-clicked.
		/// </summary>
		/// <param name="Sender">The NotifyIcon that was double-clicked.</param>
		/// <param name="e">No arguments.</param>
		private static void TrayIcon_DoubleClick(object Sender, EventArgs e) {
			// if we have a default item, execute it
			if (_Default != null) {
				ExecuteOption(_Default);
			}
		}
	}

	/// <summary>
	/// A visual editor for editing an icon's path.
	/// </summary>
	internal class IconPathTypeEditor : FileNameEditor {
		protected override void InitializeDialog(OpenFileDialog OFD) {
			base.InitializeDialog(OFD);
			OFD.Filter = Resources.IconOpenDialogFilter;
			OFD.Title = Resources.IconOpenDialogTitle;
		}
	}

	/// <summary>
	/// A visual editor for editing an executable's path.
	/// </summary>
	internal class ExecutablePathTypeEditor : FileNameEditor {
		protected override void InitializeDialog(OpenFileDialog OFD) {
			base.InitializeDialog(OFD);
			OFD.Filter = Resources.ApplicationOpenDialogFilter;
			OFD.Title = Resources.ApplicationOpenDialogTitle;
		}
	}

	/// <summary>
	/// The set of possible types of menu item.  Each type of menu item does something different when clickd.
	/// </summary>
	internal enum MenuItemType {
		/// <summary>
		/// Menu items of this type execute Black Lightning when clicked.
		/// </summary>
		BlackLightning,

		/// <summary>
		/// Menu items of this type display web sites when clicked.
		/// </summary>
		WebSite,

		/// <summary>
		/// Menu items of this type execute arbitrary applications when clicked.
		/// </summary>
		Application,

		/// <summary>
		/// Menu items of this type just display a separator and can't be clicked.
		/// </summary>
		Separator
	}
}